#include <networklib.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/select.h>
#include <netdb.h>

#include <errno.h>
#include <string.h>
#include  <malloc.h>
/*
 * connect_to_addr:
 *
 * char* ip_addr: The ip address of the server.
 * int proto: The protocol to use.
 * int port: The port numner to use. Use 0 for IANA standard ports.
 *
 * Return:
 *    socket desc - Success
 *    TODO:
 *    -1 - Remote server not listening on port
 *    -2 - Remote server not accepting connections on port.
 *    -3 - Could not create client socket.
 *    -4 - Other errors.
 */
int connect_to_addr(char* ip_addr, int port, int ip_version)
{
  int sock = 0;
  int error = 0;
  int conn_resp = 0;
  int socket_domain = AF_INET;
  int socket_type = SOCK_STREAM;
  int socket_proto = 0;
  
  struct sockaddr_in server_address_v4;
  struct sockaddr_in6 server_address_v6;
  struct hostent server;

  /* Set the family based on the IP version */
  if(ip_version == 6) {
    socket_domain = AF_INET6;
  }
  /* For now, we support only SOCK_STREAM */
  socket_type = SOCK_STREAM;
  /* Protocol field of the socket function is always 0.
     But we still use a var so that we can use it later
     if needed.
  */
  socket_proto = 0;

  /* Create the socket and store the socket fd in sock */
  sock = socket(socket_domain, socket_type, socket_proto);
  /* Check for failues */
  if(sock < 0) {
    error = errno;
    perror("Couldn't create socket\n");
    return error;
  }

  /* Fill the addresses with 0s for init */
  memset(&server_address_v4, 0, sizeof(server_address_v4));
  memset(&server_address_v6, 0, sizeof(server_address_v6));

  /* Set the values in server_address struct */
  if(ip_version == 4) {
    server_address_v4.sin_family = socket_domain;
    server_address_v4.sin_port = htons(port);
    inet_pton(socket_domain, ip_addr, &server_address_v4.sin_addr);

    /* Try connecting to server */
    conn_resp = connect( sock,
			(struct sockaddr*)&server_address_v4,
			sizeof(server_address_v4));
   
  } else {
    server_address_v6.sin6_family = socket_domain;
    server_address_v6.sin6_port = htons(port);
    inet_pton(socket_domain, ip_addr, &server_address_v6.sin6_addr);
    
    /* Try connecting to server */
    conn_resp = connect( sock,
			 (struct sockaddr*)&server_address_v6,
			 sizeof(server_address_v6));
  }

  if(conn_resp < 0) {
    error = errno;
    perror("Error in connection\n");
    printf("Received error:%d\n", error);
    return error;
  }
  
  return sock;
}


/*
 * connect_to_host:
 *
 * char* ip_addr: The ip address of the server.
 * int proto: The protocol to use.
 * int port: The port numner to use. Use 0 for IANA standard ports.
 *
 * Return:
 *    socket desc - Success
 *    -1 - Remote server not listening on port
 *    -2 - Remote server not accepting connections on port.
 *    -3 - Could not create client socket.
 *    -4 - Other errors.
 */
int connect_to_host(char* host_name, int port, int ip_version)
{
  char* ip_addr;
  struct sockaddr_in server;
  struct hostent *host;
  if(ip_version == 4) {
    /* Max size is 123.123.123.123 = 15 bytes. */
    ip_addr = (char*) malloc(15);
  } else {
    ip_addr = (char*) malloc(40);
  }

  host = gethostbyname(host_name);
  memcpy((char*)&server.sin_addr.s_addr, (char*)&host->h_addr_list[0], sizeof(host->h_length));
  ip_addr = (char*)inet_ntoa(server.sin_addr);
  return connect_to_addr(ip_addr, port, ip_version);
}

/* 
   The actual functions that sends the data to the remote connection
   using the socket. Since socket are like file descriptors, a write
   call is sufficient to transfer the data. send can be used as well.
*/
int send_data(int sock, char* data)
{
  int bytes = 0;
  bytes = write(sock, (char*)data, strlen(data));
  return bytes;
}

/*
  The actual function that recieves the data from the remote connection
  using the socket descriptor. Since the sockets are like file descrip-
  tors, a read call is sufficient. A recv can also be used.
*/
char* recv_data(int sock)
{
  char* buffer = (char*)malloc(1024);
  char* complete_data = (char*) malloc(1024);
  int bytes = 0;
  int total_bytes = 0;
  int data_rcvd = 0;
  fd_set r_sock_fds;
  struct timeval t;

  FD_ZERO(&r_sock_fds);
  FD_SET(sock, &r_sock_fds);
  
  t.tv_sec = 5;
  t.tv_usec = 0;

  while(1) {
      data_rcvd = select(sock+1, &r_sock_fds, NULL, NULL, &t);
      if(data_rcvd == -1) {
	return "\r\n";
      } else if(data_rcvd && FD_ISSET(sock, &r_sock_fds)) {
	bytes = recv(sock, buffer, 1024, 0);
	total_bytes += bytes;
	strncat(complete_data, buffer, bytes);
	/* Even if we have read 512 bytes, we relloc and make sure
	   we have about 1024 bytes more than the current size. */
	if (bytes >= 512) {
	  complete_data = (char*)realloc(complete_data, total_bytes + 1024);
	}
      } else {
	close(sock);
	break;
      }
  }
  return complete_data;
}

/* 
   Close the socket. Just a utility function. This can be done by the
   library user as well :)
*/
void close_conn(int socket)
{
  close(socket);
}

/* Data handler as a callback */
char* data_hdlr(char* data)
{
  return data;
}
